// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_HEAP_ARRAY_BUFFER_TRACKER_INL_H_
#define V8_HEAP_ARRAY_BUFFER_TRACKER_INL_H_

#include "src/conversions-inl.h"
#include "src/heap/array-buffer-tracker.h"
#include "src/heap/heap-inl.h"
#include "src/heap/spaces-inl.h"
#include "src/objects.h"
#include "src/objects/js-array-buffer-inl.h"

namespace v8 {
namespace internal {

    void ArrayBufferTracker::RegisterNew(Heap* heap, JSArrayBuffer buffer)
    {
        if (buffer->backing_store() == nullptr)
            return;

        // ArrayBuffer tracking works only for small objects.
        DCHECK(!heap->IsLargeObject(buffer));

        const size_t length = buffer->byte_length();
        Page* page = Page::FromHeapObject(buffer);
        {
            base::MutexGuard guard(page->mutex());
            LocalArrayBufferTracker* tracker = page->local_tracker();
            if (tracker == nullptr) {
                page->AllocateLocalTracker();
                tracker = page->local_tracker();
            }
            DCHECK_NOT_NULL(tracker);
            tracker->Add(buffer, length);
        }

        // TODO(wez): Remove backing-store from external memory accounting.
        // We may go over the limit of externally allocated memory here. We call the
        // api function to trigger a GC in this case.
        reinterpret_cast<v8::Isolate*>(heap->isolate())
            ->AdjustAmountOfExternalAllocatedMemory(length);
    }

    void ArrayBufferTracker::Unregister(Heap* heap, JSArrayBuffer buffer)
    {
        if (buffer->backing_store() == nullptr)
            return;

        Page* page = Page::FromHeapObject(buffer);
        const size_t length = buffer->byte_length();
        {
            base::MutexGuard guard(page->mutex());
            LocalArrayBufferTracker* tracker = page->local_tracker();
            DCHECK_NOT_NULL(tracker);
            tracker->Remove(buffer, length);
        }

        // TODO(wez): Remove backing-store from external memory accounting.
        heap->update_external_memory(-static_cast<intptr_t>(length));
    }

    Space* LocalArrayBufferTracker::space() { return page_->owner(); }

    template <typename Callback>
    void LocalArrayBufferTracker::Free(Callback should_free)
    {
        size_t freed_memory = 0;
        Isolate* isolate = page_->heap()->isolate();
        for (TrackingData::iterator it = array_buffers_.begin();
             it != array_buffers_.end();) {
            // Unchecked cast because the map might already be dead at this point.
            JSArrayBuffer buffer = JSArrayBuffer::unchecked_cast(it->first);
            const size_t length = it->second.length;

            if (should_free(buffer)) {
                JSArrayBuffer::FreeBackingStore(isolate, it->second);
                it = array_buffers_.erase(it);
                freed_memory += length;
            } else {
                ++it;
            }
        }
        if (freed_memory > 0) {
            page_->DecrementExternalBackingStoreBytes(
                ExternalBackingStoreType::kArrayBuffer, freed_memory);

            // TODO(wez): Remove backing-store from external memory accounting.
            page_->heap()->update_external_memory_concurrently_freed(
                static_cast<intptr_t>(freed_memory));
        }
    }

    template <typename MarkingState>
    void ArrayBufferTracker::FreeDead(Page* page, MarkingState* marking_state)
    {
        // Callers need to ensure having the page lock.
        LocalArrayBufferTracker* tracker = page->local_tracker();
        if (tracker == nullptr)
            return;
        tracker->Free([marking_state](JSArrayBuffer buffer) {
            return marking_state->IsWhite(buffer);
        });
        if (tracker->IsEmpty()) {
            page->ReleaseLocalTracker();
        }
    }

    void LocalArrayBufferTracker::Add(JSArrayBuffer buffer, size_t length)
    {
        page_->IncrementExternalBackingStoreBytes(
            ExternalBackingStoreType::kArrayBuffer, length);

        AddInternal(buffer, length);
    }

    void LocalArrayBufferTracker::AddInternal(JSArrayBuffer buffer, size_t length)
    {
        auto ret = array_buffers_.insert(
            { buffer,
                { buffer->backing_store(), length, buffer->backing_store(),
                    buffer->is_wasm_memory() } });
        USE(ret);
        // Check that we indeed inserted a new value and did not overwrite an existing
        // one (which would be a bug).
        DCHECK(ret.second);
    }

    void LocalArrayBufferTracker::Remove(JSArrayBuffer buffer, size_t length)
    {
        page_->DecrementExternalBackingStoreBytes(
            ExternalBackingStoreType::kArrayBuffer, length);

        TrackingData::iterator it = array_buffers_.find(buffer);
        // Check that we indeed find a key to remove.
        DCHECK(it != array_buffers_.end());
        DCHECK_EQ(length, it->second.length);
        array_buffers_.erase(it);
    }

} // namespace internal
} // namespace v8

#endif // V8_HEAP_ARRAY_BUFFER_TRACKER_INL_H_
